package com.java.diagnostics.visualizer.gc.realtime.parser;

import com.java.diagnostics.visualizer.data.DataLevel;
import com.java.diagnostics.visualizer.data.SourceData;
import com.java.diagnostics.visualizer.data.StructuredData;
import com.java.diagnostics.visualizer.data.TupleData;
import com.java.diagnostics.visualizer.data.TupleDataBuilder;
import com.java.diagnostics.visualizer.data.UnstructuredData;
import com.java.diagnostics.visualizer.data.axes.AxisPair;
import com.java.diagnostics.visualizer.data.axes.XDataAxis;
import com.java.diagnostics.visualizer.data.axes.YDataAxis;
import com.java.diagnostics.visualizer.factory.DataFactory;
import com.java.diagnostics.visualizer.factory.SourceDataFactory;
import com.java.diagnostics.visualizer.gc.defaultextensions.util.Messages;
import com.java.diagnostics.visualizer.impl.factory.LogFactory;
import com.java.diagnostics.visualizer.metadata.TupleMetaData;
import com.java.diagnostics.visualizer.metadata.TupleMetaData.TableType;
import com.java.diagnostics.visualizer.parser.vgc.InstanceVGCParser;
import com.java.diagnostics.visualizer.parser.vgc.VGCParserPreferenceHelper;
import com.java.diagnostics.visualizer.parser.vgc.constants.VGCAxes;
import com.java.diagnostics.visualizer.parser.vgc.constants.VGCGCReasons;
import com.java.diagnostics.visualizer.parser.vgc.converters.GCNumberConverter;
import com.java.diagnostics.visualizer.parser.vgc.j9.J950DateConverter;
import com.java.diagnostics.visualizer.parser.xml.GCAndMemoryVisualizerXMLParser;
import com.java.diagnostics.visualizer.properties.OutputProperties;
import com.java.diagnostics.visualizer.sources.Source;
import java.text.MessageFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xml.sax.Attributes;

public abstract class RTXMLParser
  extends GCAndMemoryVisualizerXMLParser
  implements InstanceVGCParser
{
  public static final SourceDataFactory SOURCE_DATA_FACTORY =SourceDataFactory.getFactory(); ;
  
  private static final String GLOBAL = "global";
  
  private static final String IBM_J9 = "IBM J9 ";
  
  public static final Logger TRACE = LogFactory.getTrace(RTXMLParser.class);
  
  protected final String className = getClass().getName();
  
  protected static final int UNSET = -1;
  
  protected static final String INTERVALMS = "intervalms";
  
  protected static final String TYPE = "type";
  
  protected static final String GC = "gc";
  
  protected static final String VERBOSEGC_OPEN = "<verbosegc";
  
  protected static final String VERBOSEGC_CLOSE = "</verbosegc>";
  
  protected static final String VERBOSEGC = "verbosegc";
  
  protected static final MessageFormat SCOPE_COMMENT = new MessageFormat(
    Messages.getString("StrictJ950VGCParser.scope.comment"));
  
  protected static final MessageFormat REASON_COMMENT = new MessageFormat(
    Messages.getString("StrictJ950VGCParser.reason.comment"));
  
  protected static final MessageFormat COMBINED_COMMENT = new MessageFormat(
    Messages.getString("StrictJ950VGCParser.combined.comment"));
  
  protected static final String EMPTY_STRING = "";
  
  public static final int MAX_SOURCE_SIZE = 3145728;
  
  public static final String CLOSE_ANGLE_BRACKET = ">";
  
  public static final String OPEN_ANGLE_BRACKET = "<";
  
  public static final String TEMP_FILE_PREFIX = "j9vgcparser";
  
  public static final String FILENAME_EXTENSION = ".vgc";
  
  private static final String VERSION = Messages.getString("VGCXMLParser.j9.version");
  
  protected static final String ID = "id";
  
  protected static final String TIMESTAMP = "timestamp";
  
  protected static final String METRONOME_VERSION_SUFFIX = "-Metronome";
  
  protected static final String XML_CONTENT_TYPE = "xml";
  
  private static final VGCParserPreferenceHelper PLUGIN_PREFERENCES = new VGCParserPreferenceHelper();
  
  protected String currentComment;
  
  protected int currentGCNumber;
  
  protected TupleDataBuilder gcReasons;
  
  protected TupleDataBuilder restartTuple;
  
  protected GCNumberConverter gcNumberConverter;
  
  protected XDataAxis xAxis;
  
  protected boolean isGoodData;
  
  protected double[] timestamps;
  
  protected double[] durations;
  
  protected int restartCount = 0;
  
  protected int reasonIndex = -1;
  
  protected YDataAxis heapAxis;
  
  protected boolean interpolateTimestamps;
  
  protected boolean filterSystemGCs;
  
  protected String version;
  
  protected OutputProperties properties;
  
  protected final double convertStringToNumber(String stringValue)
  {
    double value = -1.0D;
    
    if ((stringValue != null) && (stringValue.length() > 0)) {
      try {
        value = Double.parseDouble(stringValue);
      }
      catch (NumberFormatException localNumberFormatException)
      {
        value = J950DateConverter.parseDate(stringValue);
      }
    }
    
    return value;
  }
  
  protected void initialiseGeneralSourceData(Source source, OutputProperties properties) {
    this.currentComment = "";
    
    this.currentGCNumber = 0;
    
    this.heapAxis = VGCAxes.prepareHeapAxis(properties);
    this.gcNumberConverter = new GCNumberConverter();
    this.xAxis = VGCAxes.prepareXAxis(source, this.gcNumberConverter, properties);
    
    AxisPair gcReasonsPair = factory.createAxisPair(this.xAxis, 
      VGCAxes.prepareGCReasonsAxis(properties));
    this.gcReasons = factory
    
      .createTupleData("VGCLabels.gc.reasons", Messages.getString("VGCLabels.gc.reasons"), gcReasonsPair);
    this.gcReasons.setTupleMetaData(new TupleMetaData(TupleMetaData.TableType.FREQUENCY, null));
    
    this.timestamps = new double[VGCGCReasons.maxInt() + 1];
    this.durations = new double[VGCGCReasons.maxInt() + 1];
    this.restartCount = 0;
    AxisPair restartPair = factory.createAxisPair(this.xAxis, 
      VGCAxes.prepareRestartAxis(properties));
    this.restartTuple = factory
      .createTupleData("VGCLabels.jvm.restarts", Messages.getString("VGCLabels.jvm.restarts"), restartPair);
    this.restartTuple.setTupleMetaData(new TupleMetaData(TupleMetaData.TableType.COUNT, null));
    
    initialiseSpecificSourceData(properties);
  }
  
  protected abstract void initialiseSpecificSourceData(OutputProperties paramOutputProperties);
  
  protected final void addData(TupleData data)
  {
    this.sourceData.addData(data, DataLevel.VARIANT);
  }
  
  protected final void addData(StructuredData data) {
    this.sourceData.addData(data, DataLevel.VARIANT);
  }
  
  protected final void addData(UnstructuredData data) {
    this.sourceData.addData(data, DataLevel.VARIANT);
  }
  
  protected boolean isStartOfGCStanza(Attributes atts)
  {
    return atts.getValue("timestamp") != null;
  }
  
  protected String stripBadCharacters(String string)
  {
    return string;
  }
  
  protected void calculateTimestamp(Attributes atts, String reason, double intervalInMilliseconds)
  {
    double currentTimeStamp = -1.0D;
    this.reasonIndex = VGCGCReasons.nameToInt(reason);
    
    String type = atts.getValue("type");
    
    if ("global".equals(type)) {
      this.reasonIndex = this.timestamps.length;
    }
    
    double nominalTimeStamp = convertStringToNumber(
      atts.getValue("timestamp"));
    
    if ((this.reasonIndex >= 0) && (this.interpolateTimestamps)) {
      currentTimeStamp = interpolateTimestamp(intervalInMilliseconds, 
        nominalTimeStamp);
    } else {
      if (this.reasonIndex == -1) {
        TRACE.warning("Could not calculate timestamp for " + reason);
      }
      currentTimeStamp = nominalTimeStamp;
    }
    
    if (currentTimeStamp != -1.0D) {
      this.currentGCNumber += 1;
      this.xAxis.setX(currentTimeStamp);
      this.gcNumberConverter.addPoint(this.xAxis.getSequenceUID(), this.currentGCNumber);
    }
  }
  
  private double interpolateTimestamp(double intervalInMilliseconds, double nominalTimeStamp)
  {
    double previousTimestamp = this.xAxis.getX();
    
    double interval = intervalInMilliseconds;
    double relevantTimestamp = this.timestamps[this.reasonIndex];
    double currentTimeStamp; 
    if (relevantTimestamp > 0.0D)
    {
      double pause = this.durations[this.reasonIndex];
      double derivedTimeStamp = relevantTimestamp + (interval + pause) / 
        1000.0D;
      
      //double currentTimeStamp;
      
      //double currentTimeStamp;
      
      if ((Math.abs(nominalTimeStamp - derivedTimeStamp) < 1.0D) && 
        (derivedTimeStamp >= previousTimestamp)) {
        currentTimeStamp = derivedTimeStamp;
      } else {
        //double currentTimeStamp;
        if (derivedTimeStamp < previousTimestamp) {
          if (TRACE.isLoggable(Level.FINER)) {
            TRACE.finer("Time appears to go backwards for type " + 
              VGCGCReasons.intToName(this.reasonIndex) + " by " + (
              previousTimestamp - derivedTimeStamp) + 
              " s. \nReported interval is " + interval + 
              " ms.\nReported duration of last event is " + 
              pause + " ms.\n" + "Derived: " + 
              new Date((long) (1000.0D * derivedTimeStamp)) + 
              "\n previous: " + 
              new Date((long) (1000.0D * previousTimestamp)) + 
              "\n last current: " + 
              new Date((long) (1000.0D * relevantTimestamp)));
          }
          currentTimeStamp = previousTimestamp;
        }
        else
        {
          if (TRACE.isLoggable(Level.FINER)) {
            TRACE.finer("Slipped interpolating timestamp on type " + 
              VGCGCReasons.intToName(this.reasonIndex) + " by " + (
              nominalTimeStamp - derivedTimeStamp) + 
              " s. \nReported interval is " + interval + 
              " ms.\nReported duration of last event is " + 
              pause + " ms.\n" + "Derived: " + 
              new Date((long) (1000.0D * derivedTimeStamp)) + 
              "\n nominal: " + 
              new Date((long) (1000.0D * nominalTimeStamp)) + 
              "\n last current: " + 
              new Date((long) (1000.0D * relevantTimestamp)));
          }
          
          currentTimeStamp = 
            Math.max(nominalTimeStamp, previousTimestamp);
        }
      }
      
      this.timestamps[this.reasonIndex] = currentTimeStamp;
    }
    else
    {
      currentTimeStamp = Math.max(nominalTimeStamp, previousTimestamp);
      this.timestamps[this.reasonIndex] = currentTimeStamp;
    }
    
    return currentTimeStamp;
  }
  
  protected final void addDataPoint(TupleData tuple, double y, String comment) {
    tuple.addDataPoint(y, comment);
  }
  
  protected void workOutVersion(Attributes atts) {
    this.version = atts.getValue("version");
    
    if (this.version == null)
    {
      this.version = VERSION;
    }
    else {
      this.version = ("IBM J9 " + this.version);
    }
  }
  
  protected void recordVersion()
  {
    UnstructuredData versionData = factory.createUnstructuredData(
      "VGCLabels.version", Messages.getString("VGCLabels.version"), this.version);
    addData(versionData);
  }
  
  protected void initialisePreferences() {
    this.filterSystemGCs = PLUGIN_PREFERENCES.getFilterSystemGCs();
    this.interpolateTimestamps = PLUGIN_PREFERENCES.getInterpolateTimes();
  }
}
